/* A derivative work of...
 * --
 * Minimal WSPR beacon using Si5351Arduino library sourced from
 * https://gist.github.com/NT7S/2b5555aa28622c1b3fcbc4d7c74ad926
 * --
 * Based on code from:
 * Copyright (C) 2015 - 2016 Jason Milldrum
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * --
 * [You know the drill for the rest of the GPL3.. -cjb]
 * --
 * Chris Baird,, <vk2cjb@gmail.com>
 */

#include <Wire.h>
#include "RTClib.h"
#include "TimeLib.h"
#include "LiquidCrystal_PCF8574.h"
#include "si5351.h"
#include "JTEncode.h"
#include "int.h"

#define TONE_SPACING            146   // ~1.46 Hz
#define WSPR_CTC                10672 // CTC value for WSPR
#define SYMBOL_COUNT            WSPR_SYMBOL_COUNT
#define CORRECTION              0 // Change this for your ref osc
#define TX_LED_PIN              13

// These devices are all on the one I2C bus
LiquidCrystal_PCF8574 lcd(0x27); // bog-standard I2C LCD module
RTC_DS3231 rtc; // needs to be programmed with the correct time beforehand
Si5351 si5351;

// 160
//  80
//  60
//  40 7040000 - 7039900
//  30
//  20
//  17
//  15
//  12
//  10 28124600 - 28125200
//  6
//  4
//  2

#define TIMEMODULUS 6 // (every N minutes)
#define STARTMINUTE 0
//const unsigned long freq = (7039900UL); // xxx
const unsigned long freq = (7039100UL); // xxx
const char call[7] = "VK2CJB";	// xxx
const char loc[5] = "QF57";	// xxx

const si5351_drive drive[] =
  { SI5351_DRIVE_8MA, SI5351_DRIVE_6MA, SI5351_DRIVE_4MA, SI5351_DRIVE_2MA };
const uint8_t drivedbm[] = { 10, 8, 6, 3, 0 };
int drivelevel = 0;
JTEncode jtencode;
uint8_t my_tx_buffer[SYMBOL_COUNT];

/* ---------------------------------------------------------------------- */
// Timer interrupt vector.  This toggles the variable we use to gate
// each column of output to ensure accurate timing.  Called whenever
// Timer1 hits the count set below in setup().

volatile bool proceed = false;

ISR(TIMER1_COMPA_vect)
{
  proceed = true;
}

/* ---------------------------------------------------------------------- */
// Loop through the string, transmitting one character at a time.

void encode()
{
  uint8_t i;
  jtencode.wspr_encode(call, loc, drivedbm[drivelevel], my_tx_buffer);

  si5351.drive_strength(SI5351_CLK0, drive[drivelevel]);
  // Reset the tone to 0 and turn on the output
  si5351.set_clock_pwr(SI5351_CLK0, 1);
  digitalWrite(TX_LED_PIN, HIGH);
  delay(1000);			/* let the si warm up a bit */

  // Now do the rest of the message
  for(i = 0; i < SYMBOL_COUNT; i++)
    {
      uint64_t frequency = (freq * 100) + (my_tx_buffer[i] * TONE_SPACING);
      si5351.set_freq(frequency, SI5351_CLK0);
      proceed = false;

      lcd.setCursor(6,1);
      lcd.print(i);
      lcd.setCursor(10,1);
      lcd.print(my_tx_buffer[i]);
      while (!proceed)
	continue;
    }
  // Turn off the output
  si5351.set_clock_pwr(SI5351_CLK0, 0);
  digitalWrite(TX_LED_PIN, LOW);
}


/* ---------------------------------------------------------------------- */

void lcd_warn (char* s)
{
  lcd.home();
  lcd.clear();
  lcd.print(s);
}


void setup ()
{
  int i;

  // Use the Arduino's on-board LED as a keying indicator.
  pinMode(TX_LED_PIN, OUTPUT);
  digitalWrite(TX_LED_PIN, LOW);

  // Initialize the Si5351
  // Change the 2nd parameter in init if using a ref osc other
  // than 25 MHz
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, CORRECTION);

  si5351.set_freq(freq * 100, SI5351_CLK0);
  si5351.drive_strength(SI5351_CLK0, drive[drivelevel]);
  si5351.set_clock_pwr(SI5351_CLK0, 0); // Disable the clock initially

  // Set up Timer1 for interrupts every symbol period.
  noInterrupts();          // Turn off interrupts.
  TCCR1A = 0;              // Set entire TCCR1A register to 0; disconnects
                           //   interrupt output pins, sets normal waveform
                           //   mode.  We're just using Timer1 as a counter.
  TCNT1  = 0;              // Initialize counter value to 0.
  TCCR1B = (1 << CS12) |   // Set CS12 and CS10 bit to set prescale
    (1 << CS10) |          //   to /1024
    (1 << WGM12);          //   turn on CTC
                           //   which gives, 64 us ticks
  TIMSK1 = (1 << OCIE1A);  // Enable timer compare interrupt.
  OCR1A = WSPR_CTC;        // Set up interrupt trigger count;
  interrupts();            // Re-enable interrupts.

  /* ---------------------------------------- */

  lcd.begin(16, 2); // initialize the lcd
  lcd.setBacklight(255);
  lcd.home(); lcd.clear();
  lcd.noBlink();
  lcd.noCursor();

  if (!rtc.begin())
    {
      lcd_warn("Can't find RTC");
      while (1);
    }

#if 0
  if (!rtc.isrunning())
    {
      lcd_warn("RTC NOT running");
      while (1);
    }
#endif

  const char *banners[] = { "Y.A.A.W.B", "Yet.A.A.W.B", "Y.Another.A.W.B",
    "Y.A.Arduino.W.B", "Y.A.A.WSPR.B   ", "Y.A.A.W.Beacon " };

  for (i = 0; i < 6; i++)
    {
      lcd.setCursor(0,0);
      lcd.print(banners[i]);
      lcd.setCursor(0, 1);
      lcd.print(F("VK2CJB 2018"));
      delay(1000);
    }

  lcd.home();
  lcd.clear();
}


/* ---------------------------------------------------------------------- */

void lcd_clock (int d)
{
  if (d < 0)
    {
      lcd.print (F("--"));
      return;
    }

  if (d < 10)
    lcd.print ('0');
  lcd.print(d);
  if (d == 99)			/* just to catch the countdown scrap */
    lcd.print(' ');
}


/* ---------------------------------------------------------------------- */

void loop ()
{
  int min, sec, m;

  DateTime now = rtc.now();
  min = now.minute();
  sec = now.second();

  lcd.setCursor(0,0);
  lcd_clock(now.hour());
  lcd.print(':');
  lcd_clock(min);
  lcd.print(':');
  lcd_clock(sec);

  m = min % TIMEMODULUS;
  m = STARTMINUTE - m;
  if (m < 0)
    m += TIMEMODULUS;
  m *= 60;
  m -= sec;
  lcd.setCursor(9,0);
  lcd_clock(m);

  lcd.setCursor(0,1);
  lcd.print(F("Next "));
  lcd.print(drivedbm[drivelevel]);
  lcd.print(F("dBm"));

  if (m == 0)
    {
      lcd.setCursor(0,0);
      lcd.print(F("TRANSMITTING"));
      lcd.setCursor(0,1);
      lcd.print(drivedbm[drivelevel]);
      lcd.print(F("dBm        "));
      encode();
      lcd.clear();
      drivelevel++;
      if (drivedbm[drivelevel] == 0)
	drivelevel = 0;
    }

  delay(500);
}

/* ---------------------------------------------------------------------- */
